#include "ComputationMemory.h"

using namespace std;

ComputationMemory::ComputationMemory(){
  _env = new Environment(); 
}

ComputationMemory::ComputationMemory(Environment * env){
  _env = env; 
}

ComputationMemory::~ComputationMemory(){
  delete _env;
}

void ComputationMemory::setEnvironment(Environment * env) {
  _env = env; 
}

Environment * ComputationMemory::getEnvironment(){
  return _env;
}

//Launch the number of threads needed with the sequential function
void ComputationMemory::threadSequential(){
  int nbThread = _env->getNbThread();

  for (int i=0; i< nbThread; i++){
    pthread_create(&(_env->getThread()[i]), NULL, &benchMarkSeq, (void*)_env);
  }
  for (int i =0; i< nbThread; i++){
    pthread_join(_env->getThread()[i],NULL);
  }  

}

//Launch the number of threads needed with the random function
void ComputationMemory::threadRandom(){
  int nbThread = _env->getNbThread();

  for (int i=0; i< nbThread; i++){
    pthread_create(&(_env->getThread()[i]), NULL, &benchMarkRand, (void*)_env);
  }
  for (int i =0; i< nbThread; i++){
    pthread_join(_env->getThread()[i],NULL);
  }  

}

void ComputationMemory::runTests(){
   //Set the precision of cerr to 15 digits
  cerr << setprecision(15);

  switch(_env->getAccess()){
  case 0 : // sequential & Random
    threadSequential();
    threadRandom(); 
    break; 
  case 1 : // sequential
    threadSequential(); 
    break; 
  case 2 : // random
    threadRandom();
    break; 
  default: // failed 
      cout << "Something went wrong. Please check the arguments."<< endl; 
      break; 
  }
}

void *benchMarkSeq(void * ptr){
  //Get the environment containing the computation parameters
  Environment * env = (Environment*)ptr;

  int size = env->getBlockSize();
  int i =0; 
  //allocate 100MB of memory for computations
  char * destMem = (char*) malloc(100*MB*sizeof(char));
  char * srcMem = (char*) malloc(100*MB*sizeof(char)); 

  //Fill the source memory
  for (i=0; i<100*MB; i++){
    srcMem[i] = 'p';
  }


  int  maxOperation = (100*MB);
  struct timeval tbegin, tend; 
  
  gettimeofday(&tbegin, NULL); 
  //Copy all the memory form Source to Destination with the right block size
  for (i = 0; i<maxOperation; i+=size){
    memcpy((void*)(destMem+i), (void*)(srcMem+i), size); 
  }
  gettimeofday(&tend, NULL);
  //Compute time spent
  float totalTimeSec = tend.tv_sec - tbegin.tv_sec;
  float totalTime = tend.tv_usec - tbegin.tv_usec + (totalTimeSec * 1000000);

  //Compute results
  float throughput = 100/(totalTime/1000000);
  float latency = (totalTime/1000) / (100*MB); 
  cout << "Result of Sequential Access : " << endl; 
  cout << "Throughput : " << throughput << " MB/s" <<endl; 

  //this output format is used in the script 
  cerr << throughput  << ";" << endl; 

  //The latency is displayed only if the computation was done with B size block
  if(size==B){
    cout << "Latency : "<< latency << " ms" << endl ;
    
    //this output format is used in the script 
    cerr << fixed <<  ";;" << latency << ";" << endl;
  }
  
  free(srcMem);
  free(destMem);
  return NULL;
}

void *benchMarkRand(void * ptr){
  //Get the environment containing the computation parameters
  Environment * env = (Environment*)ptr;
  
  int size, randd, maxOperations, i; 
  int * tabRandom; 
  char * destMem, *srcMem;  
  struct timeval tbegin, tend;
  size  = env->getBlockSize(); 
  randd = 0; 
  maxOperations = (MB/size)*100;

  //allocate 100MB of memory for computations  
  destMem = (char*) malloc(100*MB*sizeof(char));
  srcMem = (char*) malloc(100*MB*sizeof(char)); 

  //Fill the source memory
  for (i=0; i<100*MB; i++){
    srcMem[i] = 'p';
  }

  //Create an array of random addresses to access in the computation
  tabRandom = (int*)malloc(sizeof(int)*maxOperations) ; 
  for (i = 0; i < maxOperations; i++){
    randd = rand()%((100*MB)-size);
    tabRandom[i] = randd ; 
  }

  gettimeofday(&tbegin, NULL);

 //Copy all the memory form Source to Destination with the right block size
  for (i=0; i< maxOperations; i++) {
    memcpy((void*)(destMem+tabRandom[i]), (void*)(srcMem+tabRandom[i]), size); 

  }

  gettimeofday(&tend, NULL);

  //Compute time spent
  float totalTimeSec = tend.tv_sec - tbegin.tv_sec;
  float totalTime = tend.tv_usec - tbegin.tv_usec + (totalTimeSec*1000000);

  //Compute results
  float throughput = 100/(totalTime/1000000); 
  float latency = (totalTime/1000) / (100*MB);
  cout << "Result of Random Access : " << endl; 
  cout << "Throughput : " << throughput << " MB/s" << endl; 

 //this output format is used in the script 
  cerr << ";" << throughput << ";" << endl; 

 //The latency is displayed only if the computation was done with B size block
   if(size==B){
     cout << "Latency : "<< latency << " ms" << endl ; 
     //this output format is used in the script 
     cerr << fixed << ";;" << latency << ";" << endl;
  }
  free(tabRandom);
  free(srcMem);
  free(destMem);
  return NULL;
}
